# -*- coding: UTF-8 -*-
"""
@ProjectName: VirusTracker
本程序部分参考或使用了hack-fang/nCov项目
感谢BlankerL/DXY-COVID-19-Crawler提供的API接口
"""

import hashlib
import json
import os
import time
from json import JSONDecodeError

import bleach as bleach
import pandas as pd
import requests
import shutil

from pyecharts.globals import WarningType

WarningType.ShowWarning = False
from pyecharts.charts import Bar, Line, Map
from pyecharts import options as opts

# WordPress. Can be removed if not in use.
from wordpress_xmlrpc.exceptions import ServerConnectionError
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods import posts


# 设置项
apiURL = "https://lab.isaaclin.cn/nCoV/api/"
csvURL = "https://raw.githubusercontent.com/BlankerL/DXY-COVID-19-Data/master/csv/DXYOverall.csv"
# 若Github访问不畅可尝试使用我提供的反代
# csvURL = 'https://raw.geecloud.eu/BlankerL/DXY-COVID-19-Data/master/csv/DXYOverall.csv'
# csvURL = 'https://raw-github.geecloud.eu/BlankerL/DXY-COVID-19-Data/master/csv/DXYOverall.csv'
# Overall数据的备用网址，在api崩溃时调用
overallBackupURL = 'https://raw.geecloud.eu/BlankerL/DXY-COVID-19-Data/master/json/DXYOverall.json'
# Overall数据的备用网址，在api崩溃时调用
areaBackupURL = 'https://raw.geecloud.eu/BlankerL/DXY-COVID-19-Data/master/json/DXYArea.json'
# 渲染路径（相对于当前路径，推荐使用绝对路径）
renderDir = "render"
# 输出路径对于网站根目录的相对路径
webRootDir = "render"
# WordPress. Can be removed if not in use.
xmlRPC = 'REPLACEME'
wpUser = 'REPLACEME'
wpPassword = 'REPLACEME'

# 初始化变量
fileChange = False
historyUpdated = False


# 获取文件md5值，便于比较文件差异
def getFileMD5(filename):
    md5 = hashlib.md5()
    f = open(filename, 'rb')
    while True:
        b = f.read(8096)
        if not b:
            break
        md5.update(b)
    f.close()
    return md5.hexdigest()


# 13位毫秒时间戳转时间
def timeStamp(timeNum):
    time_Stamp = float(timeNum / 1000)
    timeArray = time.localtime(time_Stamp)
    otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
    return otherStyleTime


# 拉取最新Overall数据
def fetchOverallData():
    global overall_data
    try:
        url = apiURL + 'overall?latest=1'
        overall_data = json.loads(requests.get(url).text, encoding='utf-8')
    except JSONDecodeError:
        # 若API崩了就使用备用网址
        url = overallBackupURL
        overall_data = json.loads(requests.get(url).text, encoding='utf-8')


def getOverallData(data_parameter, region='domestic'):
    # 调用举例：get_overall_data("currentConfirmedCount", "global")
    # 调用举例：get_overall_data("currentConfirmedCount")
    od = {}  # short for "overall data"
    try:
        od = overall_data["results"][0]
        if region == 'global':
            return od['globalStatistics'][data_parameter]
        elif region == 'domestic' or 'general':
            return od[data_parameter]
        else:
            pass
    except KeyError:
        print('API returned a wrong response!')
        return None


def createGeneralCharts():
    # 表1：现存
    parameters = ["confirmedCount", "currentConfirmedCount", "curedCount", "deadCount", "suspectedCount",
                  "seriousCount"]
    domesticCounts = list()
    overseasCounts = list()

    for parameter in parameters:
        domesticCount = getOverallData(parameter)
        if domesticCount != 0:
            domesticCounts.append(domesticCount)
        else:
            domesticCounts.append(None)
        if parameter in ["suspectedCount", "seriousCount"]:
            overseasCounts.append(None)
        else:
            globalCount = getOverallData(parameter, 'global')
            overseasCounts.append(globalCount - domesticCount)
    c = (
        Bar(init_opts=opts.InitOpts(width='700px', height='500px'))
            .add_xaxis(["累计确诊", "现存确诊", "治愈人数", "死亡人数", "现存疑似", "重症人数"])
            .add_yaxis("国内", domesticCounts, stack="stack1")
            .add_yaxis("海外", overseasCounts, stack="stack1")
            .set_series_opts(
            label_opts=opts.LabelOpts(
                is_show=True,
                position="top",
                color=None,
                distance=None,
                font_size=12)
        )
            .set_global_opts(
            title_opts={"text": "当前疫情数据", "subtext": "疑似/重症无海外数据"},
            toolbox_opts=opts.ToolboxOpts(
                is_show=True,
                pos_top="top",
                pos_left="right",
                feature={"saveAsImage": {},
                         "restore": {},
                         "magicType": {"show": True, "type": ["line", "bar"]},
                         "dataView": {}})
        )
            .render("%s/bar_general.html" % renderDir)
    )


def createIncreaseCharts():
    # 表2：新增
    parameters = ["currentConfirmedIncr", "confirmedIncr", "curedIncr", "deadIncr", "suspectedIncr", "seriousIncr"]
    domesticIncrCounts = list()
    overseasIncrCounts = list()

    for parameter in parameters:
        domesticIncrCount = getOverallData(parameter)
        if domesticIncrCount != 0:
            domesticIncrCounts.append(domesticIncrCount)
        else:
            domesticIncrCounts.append(None)
        if parameter in ["suspectedIncr", "seriousIncr"]:
            overseasIncrCounts.append(None)
        else:
            globalIncrCount = getOverallData(parameter, 'global')
            overseasIncrCounts.append(globalIncrCount - domesticIncrCount)
    c = (
        Bar(init_opts=opts.InitOpts(width='700px', height='500px'))
            .add_xaxis(["现存确诊", "累计确诊", "治愈人数", "死亡人数", "国内疑似", "国内重症"])
            .add_yaxis("国内", domesticIncrCounts, stack="stack1")
            .add_yaxis("海外", overseasIncrCounts, stack="stack1")
            .set_series_opts(
            label_opts=opts.LabelOpts(
                is_show=True,
                position="top",
                color=None,
                distance=None,
                font_size=12)
        )
            .set_global_opts(
            title_opts={"text": "昨日疫情变化", "subtext": "疑似/重症无海外数据"},
            toolbox_opts=opts.ToolboxOpts(
                is_show=True,
                pos_top="top",
                pos_left="right",
                feature={"saveAsImage": {},
                         "restore": {},
                         "magicType": {"show": True, "type": ["line", "bar"]},
                         "dataView": {}})
        )

            .render("%s/bar_increase.html" % renderDir)
    )


def getHistory():
    r = requests.get(csvURL)
    global fileChange
    global historyUpdated
    if not os.path.exists('DXYOverall.txt'):
        with open("DXYOverall.txt", "wb") as download:
            download.write(r.content)
            download.close()
            fileChange = True
            historyUpdated = True
    else:
        with open("DXYOverall.LATEST.txt", "wb") as download:
            download.write(r.content)
            download.close()
        newMD5 = getFileMD5('DXYOverall.LATEST.txt')
        if newMD5 == getFileMD5('DXYOverall.txt'):
            os.remove('DXYOverall.LATEST.txt')
        else:
            os.rename('DXYOverall.LATEST.txt', "DXYOverall.txt")
            fileChange = True
            historyUpdated = True


def historyReader(parameter):
    csvFile = pd.read_csv("DXYOverall.txt")
    # 去重
    csvFile.drop_duplicates(subset=['confirmedCount', 'curedCount', 'deadCount'], keep='first')
    parameter_list = ['updateTime', 'confirmedCount', 'curedCount', 'deadCount']
    if parameter in parameter_list:
        # 抽取部分非重复数据点
        return csvFile[parameter][::-30].tolist()
    else:
        pass


def createDatazone():
    c = (
        Line(init_opts=opts.InitOpts(width='700px', height='500px'))
            .add_xaxis(historyReader('updateTime'))
            .add_yaxis("累计确诊", historyReader('confirmedCount'), is_smooth=True)
            .add_yaxis("治愈", historyReader('curedCount'), is_smooth=True)
            .add_yaxis("死亡", historyReader('deadCount'), is_smooth=True)
            .set_global_opts(
            title_opts={"text": "国内疫情变化时间轴", "subtext": "包含所有非重复数据"},
            datazoom_opts=[
                opts.DataZoomOpts(range_start=0, range_end=100),
                opts.DataZoomOpts(type_="inside", range_start=0, range_end=100),
            ],
            toolbox_opts=opts.ToolboxOpts(
                is_show=True,
                pos_top="top",
                pos_left="right",
                feature={"saveAsImage": {},
                         "dataZoom": {},
                         "restore": {},
                         "dataView": {}})
        )
            .render("%s/bar_datazoom.html" % renderDir)
    )


# 将拉取省级数据定义成函数避免重复拉取
# 注意，在执行次项如隔上一次拉取过近则会503，请使用time.sleep(2)暂停几秒


def fetchAreaData():
    global areaData
    try:
        AreaURL = apiURL + 'area?latest=1'
        areaData = json.loads(requests.get(AreaURL).text, encoding='utf-8')
    except JSONDecodeError:
        AreaURL = areaBackupURL
        areaData = json.loads(requests.get(AreaURL).text, encoding='utf-8')


# 返回全国各省的累计数据
def getAreaData(parameter='confirmedCount'):
    parameter_list = ['currentConfirmedCount', 'confirmedCount', 'curedCount', 'deadCount']
    if parameter in parameter_list:
        try:
            aD = {}
            for r in areaData['results']:
                # 去除别的国家数据
                if r.get("provinceShortName") == '中国' or None:
                    pass
                elif r.get("countryName") != "中国":
                    pass
                else:
                    aD[r["provinceShortName"]] = r[parameter]
                # 先对字典进行排序,按照value从大到小
            aD = sorted(aD.items(), key=lambda x: x[1], reverse=True)
            return aD
        except KeyError:
            print('API returned a wrong response!')
            return None
    else:
        pass


def createProvincialBar():
    AreaDataNO0 = [i for i in getAreaData('currentConfirmedCount') if i[1] != 0]
    provinceNames, currentConfirmedCount_province = zip(*AreaDataNO0)
    c = (
        Bar(init_opts=opts.InitOpts(width='700px', height='500px'))
            .add_xaxis(list(provinceNames))
            .add_yaxis('现存确诊', list(currentConfirmedCount_province))
            .set_series_opts(
            label_opts=opts.LabelOpts(
                is_show=True,
                position="top",
                color=None,
                distance=None,
                font_size=12)
        )
            .set_global_opts(
            title_opts={"text": "各省级行政区现存确诊病例", "subtext": "仅显示有病例的省级行政区"},
            legend_opts=opts.LegendOpts(is_show=False),
            toolbox_opts=opts.ToolboxOpts(
                is_show=True,
                pos_top="top",
                pos_left="right",
                feature={"saveAsImage": {},
                         "restore": {},
                         "magicType": {"show": True, "type": ["line"]},
                         "dataView": {}})
        )

            .render("%s/bar_provincial.html" % renderDir)
    )


def createMap():
    c = (
        Map(init_opts=opts.InitOpts(width='700px', height='500px'))
            .add("累计确诊", getAreaData('confirmedCount'), "china")
            .add("治愈", getAreaData('curedCount'), "china")
            .add("死亡", getAreaData('deadCount'), "china")
            .set_global_opts(
            toolbox_opts=opts.ToolboxOpts(
                is_show=True,
                pos_top="top",
                pos_left="right",
                feature={"saveAsImage": {},
                         "restore": {},
                         "dataView": {}}),
            title_opts=opts.TitleOpts(title="中国疫情地图"),
            legend_opts=opts.LegendOpts(selected_mode='single'),
            visualmap_opts=opts.VisualMapOpts(
                max_=10000, is_piecewise=True,
                pieces=[
                    {"min": 1001, "label": '>1000', "color": '#8A0808'},
                    {"max": 1000, "min": 500, "label": '500-1000', "color": '#B40404'},
                    {"max": 499, "min": 100, "label": '100-499', "color": '#DF0101'},
                    {"max": 99, "min": 10, "label": '10-99', "color": '#F78181'},
                    {"max": 9, "min": 1, "label": '1-9', "color": '#F5A9A9'},
                    {"max": 0, "min": 0, "label": '0', "color": '#FFFFFF'},
                ])
        )
            .render("%s/map.html" % renderDir)
    )


# 测试函数区
# 获取OverallData并绘制两个有关图表
# fetchOverallData()
# createIncreaseCharts()
# createGeneralCharts()
# timeStamp(getOverallData('updateTime', 'overall'))

# 获取历史数据并绘制时间轴
# getHistory()
# print (fileChange)
# createDatazone()

# 暂停2秒，防止API返回503
# time.sleep(2)

# 获取省级信息
# fetchAreaData()
# print(getAreaData('confirmedCount'))
# createProvincialBar()
# createMap()
# exit()

# WordPress. Can be removed if not in use.
try:
    client = Client(xmlRPC, wpUser, wpPassword)
except ServerConnectionError:
    print('登录失败')


# 主程序部分
# 检查是否发生改变
fetchOverallData()
try:
    fileList = open('fileList.txt')
    lines = fileList.readlines()
    fileList.close()
    fileList_dict = {}
    for line in lines:
        s = line.split()
        fileList_dict[s[0]] = s[1]
    fileList.close()
    fileList_Parameters = ['OverallDataTime', 'AreaDataMD5']
    OverallDataTime = int(fileList_dict.get('OverallDataTime'))
    AreaDataMD5 = fileList_dict.get('AreaDataMD5')
# 若无法打开文件并获取对应数值则先初始化变量
except IOError and IndexError and FileNotFoundError:
    OverallDataTime = 0
    AreaDataMD5 = ''

# 判断OverallData是否发生变化
getOverallDataTime = getOverallData('updateTime', 'general')
if getOverallDataTime != OverallDataTime:
    # 更新增长与疫情总表
    createIncreaseCharts()
    createGeneralCharts()
    fileChange = True
    OverallDataTime = getOverallDataTime

# API有调用频率限制，等待1秒
time.sleep(1)
# 判断AreaData是否发生变化
fetchAreaData()
getAreaDataMD5 = hashlib.md5(str(areaData).encode('utf8')).hexdigest()
if AreaDataMD5 != getAreaDataMD5:
    createProvincialBar()
    createMap()
    AreaDataMD5 = getAreaDataMD5

# 判断Histroy（历史数据）是否发生变化，并自动更新数据
getHistory()
if historyUpdated:
    createDatazone()

if fileChange:
    # 写入特征值至文件
    fileList_new = open('fileList.txt', 'w')
    fileList_dict_new = {}
    fileList_dict_new['OverallDataTime'] = [str(OverallDataTime)]
    fileList_dict_new['AreaDataMD5'] = [str(AreaDataMD5)]
    data_write = ''
    for n in fileList_dict_new:
        line = n + ' ' + ''.join(fileList_dict_new[n]) + '\n'
        data_write += line
    fileList_new.write(data_write)
    fileList_new.close()

    # 复制文件
    timeUnix = getOverallData('updateTime', 'general')
    timeReadable = str(timeStamp(timeUnix))
    newDir = webRootDir + '/history/' + str(timeUnix)
    try:
        shutil.copytree(renderDir, newDir, ignore=shutil.ignore_patterns("history"))
    except FileExistsError:
        shutil.rmtree(newDir)
        shutil.copytree(renderDir, newDir, ignore=shutil.ignore_patterns("history"))
    # 目标文件路径
    historyIncrease = '/%s/bar_increase.html' % newDir
    historyGeneral = '/%s/bar_general.html' % newDir
    historyProvincial = '/%s/bar_provincial.html' % newDir
    historyMap = '/%s/map.html' % newDir
    historyDatazone = '/%s/bar_datazoom.html' % newDir
    # 推送文章
    print('Data changed! Updating relevant charts!')
    try:
        client = Client(xmlRPC, wpUser, wpPassword)
    except ServerConnectionError:
        print('Login Failed!')
    else:
        postContent = ''
        ContentHead = '<iframe width=\"100%\" height=\"520\" src=\"'
        contentTail = '\" frameborder=\"0\" allowfullscreen=\"\"></iframe>\n\n'
        # 合成文章数据
        for i in [historyIncrease, historyGeneral, historyProvincial, historyMap, historyDatazone]:
            postContent = postContent + ContentHead + i + contentTail
        post_content_insert = bleach.clean(postContent)
        post_content_insert = post_content_insert.replace('&lt;', '<')
        post_content_insert = post_content_insert.replace('&gt;', '>')

        historyPost = WordPressPost()
        historyPost.title = '疫情数据 %s' % timeReadable
        historyPost.content = post_content_insert
        historyPost.post_status = 'publish'
        historyPost.terms_names = {
            'category': '历史数据'
        }
        # Post！
        historyPost.id = client.call(posts.NewPost(historyPost))
